package com.api.apigateway.risk;

import java.util.HashMap;
import java.util.Map;

/**
 * 风控服务
 * 运行逻辑：
 * 1) 提取特征（FeatureExtractor）
 * 2) 规则引擎打分（RulesEngine）
 * 3) 模型打分（ModelScorer）
 * 4) 融合为综合分（composite = w_rule*rule + w_model*model）
 * 5) 根据阈值返回：放行 / 加严 / 拦截
 */
public class RiskService {

    private final FeatureExtractor extractor;
    private final RulesEngine rules;
    private final ModelScorer model;

    //
    /**
     * 权重与阈值（可配置）
     * 这几个参数定义了融合策略：
     *  规则和模型各占 50%。
     *  最终综合分 ≥0.85 → 拦截，≥0.6 → 加严，否则放行。
     * 都可以放配置中心，便于随时调节。
     */
    private double wRule = 0.5;        // 规则分的权重（默认 50%）
    private double wModel = 0.5;       // 模型分的权重（默认 50%）
    private double tightenThreshold = 0.6; // 加严阈值（综合分 ≥ 0.6 → 验证码/灰度）
    private double blockThreshold   = 0.85; // 拦截阈值（综合分 ≥ 0.85 → 直接拒绝）

    public RiskService(FeatureExtractor extractor, RulesEngine rules, ModelScorer model){
        this.extractor = extractor;
        this.rules = rules;
        this.model = model;
    }

    /** 计算决策（主入口）  (0 表示正常、1 表示异常)*/
    public RiskDecision decide(RiskContext ctx){
        FeatureVector fv = extractor.extract(ctx);

        Map<String,String> tags = new HashMap<String,String>();//存储规则命中的标签（比如 ["UA_TOO_SHORT", "BURST_QPS"]）。
        double rScore = rules.eval(ctx, fv, tags); //规则分。
        double mScore = model.score(fv); //模型分（来自 iforest.onnx，取 1 - scores）。

        RiskScore rs = new RiskScore();
        rs.ruleScore = rScore;
        rs.modelScore = mScore;
        //综合分：= 规则分 × 权重 + 模型分 × 权重。
        //用 clamp() 保证在 [0,1] 之间。
        rs.composite = clamp(wRule*rScore + wModel*mScore);

        String reason = composeReason(tags, rs);

        /**
         * 核心决策逻辑：
         *  综合分 ≥ 0.85 → block()（直接拒绝）
         *  0.6 ≤ 综合分 < 0.85 → tighten()（验证码 / 人机验证 / 二次校验）
         *  否则 → pass()（放行）
         */
        if (rs.composite >= blockThreshold){
            return RiskDecision.block(reason, rs.composite, rScore, mScore);
        }else if (rs.composite >= tightenThreshold){
            return RiskDecision.tighten(reason, rs.composite, rScore, mScore);
        }else{
            return RiskDecision.pass(reason, rs.composite, rScore, mScore);
        }
    }

    /**
     * 生成一个可读原因字符串，用于日志和审计。
     * 格式：rule=0.400,model=0.220,tags=[UA_TOO_SHORT, BURST_QPS]
     * @param tags
     * @param rs
     * @return
     */
    private static String composeReason(Map<String,String> tags, RiskScore rs){
        return "rule=" + round(rs.ruleScore) + ",model=" + round(rs.modelScore) + ",tags=" + tags.keySet();
    }

    private static double clamp(double v){ return Math.max(0.0, Math.min(1.0, v)); }
    private static String round(double d){ return String.format(java.util.Locale.US, "%.3f", d); }
}